using System;
using System.Collections.Generic;
using System.Text;

namespace UserInterface.MML.Base
{
    internal class Parser
    {
        private static List<string> selfClosingTagNames;

        public static List<string> SelfClosingTagNames
        {
            get { return selfClosingTagNames; }
            set { selfClosingTagNames = value; }
        }

        private static bool IsSelfClosingTag(string name)
        {
            return selfClosingTagNames == null
                || selfClosingTagNames.Contains(name);
        }
        
        private List<Token> tokens;
        private int index = 0;
        private ParseTree parseTree = new ParseTree();

        public Parser(List<Token> tokens)
        {
            this.tokens = tokens;
            PerformParse();
        }

        public ParseTree ParseTree
        {
            get { return parseTree; }
        }

        private void PerformParse()
        {
            while (index < tokens.Count)
            {
                bool isValid = false;
                Token currentToken = tokens[index];
                if (currentToken is CharacterDataToken)
                {
                    ParseCharacterDataToken(currentToken as CharacterDataToken);
                    isValid = true;
                }
                else if (currentToken is StartTagStartToken)
                {
                    if (ParseCandidateTag(currentToken as StartTagStartToken))
                    {
                        isValid = true;
                    }
                }
                else if (currentToken is EndTagToken)
                {
                    if (ParseEndTag(currentToken as EndTagToken))
                    {
                        isValid = true;
                    }
                }
                else
                {
                    index++;
                }
                if (!isValid)
                {
                    parseTree.Items.Add(new CharacterDataItem(currentToken.Code));
                }
            }
            CleanRemainingCandidateTags();
            CombineAdjacentCharacterDataItems(parseTree);
        }

        private void CleanRemainingCandidateTags()
        {
            int itemCount = parseTree.Items.Count;
            for (int i = 0; i < itemCount; i++)
            {
                CandidateTag candidateTag = parseTree.Items[i] as CandidateTag;
                if (candidateTag != null)
                {
                    parseTree.Items.Remove(candidateTag);
                    if (IsSelfClosingTag(candidateTag.Name))
                    {
                        TagItem tagItem = CreateTagItemForCandidate(candidateTag, null);
                        parseTree.Items.Insert(i, tagItem);
                    }
                    else
                    {
                        string code = candidateTag.GetOriginalCode();
                        parseTree.Items.Insert(i, new CharacterDataItem(code));
                    }
                }
            }
        }

        private TagItem CreateTagItemForCandidate(CandidateTag candidateTag, string endTagCode)
        {
            TagItem tagItem = new TagItem(
                candidateTag.Name,
                candidateTag.Value,
                candidateTag.GetOriginalCode(),
                endTagCode
            );
            foreach (CandidateTagAttribute candidateAttribute in candidateTag.Attributes.Values)
            {
                tagItem.AddAttribute(
                    candidateAttribute.Name,
                    candidateAttribute.Value,
                    candidateAttribute.Code
                );
            }
            return tagItem;
        }

        private void ParseCharacterDataToken(CharacterDataToken token)
        {
            parseTree.Items.Add(new CharacterDataItem(token.Data));
            index++;
        }

        private bool ParseCandidateTag(StartTagStartToken startTagStartToken)
        {
            CandidateTag candidateTag = new CandidateTag(startTagStartToken.Name);
            candidateTag.Tokens.Add(startTagStartToken);

            int localIndex = index + 1;
            Token lastToken = startTagStartToken;
            string lastAttributeName = null;
            StringBuilder lastAttributeCode = new StringBuilder();
            bool endTagTokenFound = false;
            while (!endTagTokenFound)
            {
                if (localIndex >= tokens.Count)
                {
                    index++;
                    return false;
                }

                bool isValid = false;
                Token token = tokens[localIndex];

                if (token is AttributeNameToken
                    && (lastToken is StartTagStartToken
                        || lastToken is ValueToken
                        || lastToken is AttributeNameToken))
                {
                    if (lastAttributeName != null)
                    {
                        string code = lastAttributeCode.ToString();
                        candidateTag.AddAttribute(lastAttributeName, null, code);
                        lastAttributeCode = new StringBuilder();
                        lastAttributeName = null;
                    }
                    AttributeNameToken attributeNameToken = token as AttributeNameToken;
                    lastAttributeName = attributeNameToken.Name;
                    lastAttributeCode.Append(attributeNameToken.Code);
                    isValid = true;
                }
                else if (token is AssignmentToken
                    && (lastToken is StartTagStartToken
                        || lastToken is AttributeNameToken))
                {
                    if (lastToken is AttributeNameToken)
                    {
                        AssignmentToken attributeAssignmentToken = token as AssignmentToken;
                        lastAttributeCode.Append(attributeAssignmentToken.Code);
                    }
                    isValid = true;
                }
                else if (token is ValueToken
                    && lastToken is AssignmentToken)
                {
                    ValueToken valueToken = token as ValueToken;
                    string value = valueToken.Value;
                    if (lastAttributeName == null)
                    {
                        candidateTag.Value = value;
                    }
                    else
                    {
                        lastAttributeCode.Append(valueToken.Code);
                        string code = lastAttributeCode.ToString();
                        candidateTag.AddAttribute(lastAttributeName, value, code);
                        lastAttributeName = null;
                        lastAttributeCode = new StringBuilder();
                    }
                    isValid = true;
                }
                else if (token is StartTagEndToken)
                {
                    endTagTokenFound = true;
                    isValid = true;
                }

                if (isValid)
                {
                    candidateTag.Tokens.Add(token);
                    lastToken = token;
                    localIndex++;
                }
                else
                {
                    index++;
                    return false;
                }
            }

            if (lastAttributeName != null)
            {
                string code = lastAttributeCode.ToString();
                candidateTag.AddAttribute(lastAttributeName, null, code);
            }

            parseTree.Items.Add(candidateTag);
            index = localIndex;
            return true;
        }

        private CandidateTag FindLastCandidateTagWithName(string name)
        {
            int itemCount = parseTree.Items.Count;
            CandidateTag candidateTag;
            for (int i = itemCount - 1; i >= 0; i--)
            {
                candidateTag = parseTree.Items[i] as CandidateTag;
                if (candidateTag != null && candidateTag.Name == name)
                {
                    return candidateTag;
                }
            }
            return null;
        }

        private bool ParseEndTag(EndTagToken endTagToken)
        {
            index++;
            CandidateTag candidateTag = FindLastCandidateTagWithName(endTagToken.Name);
            if (candidateTag == null)
            {
                return false;
            }
            int candidateTagIndex = parseTree.Items.IndexOf(candidateTag);
            parseTree.Items.Remove(candidateTag);
            TagItem tagItem = CreateTagItemForCandidate(candidateTag, endTagToken.Code);
            while (candidateTagIndex < parseTree.Items.Count)
            {
                IItem subItem = parseTree.Items[candidateTagIndex];
                parseTree.Items.Remove(subItem);
                CandidateTag subCandidateTag = subItem as CandidateTag;
                if (subCandidateTag == null)
                {
                    tagItem.Items.Add(subItem);
                }
                else
                {
                    if (IsSelfClosingTag(subCandidateTag.Name))
                    {
                        TagItem subTagItem = CreateTagItemForCandidate(subCandidateTag, null);
                        tagItem.Items.Add(subTagItem);
                    }
                    else
                    {
                        string subCode = subCandidateTag.GetOriginalCode();
                        tagItem.Items.Add(new CharacterDataItem(subCode));
                    }
                }
            }
            parseTree.Items.Add(tagItem);
            return true;
        }

        private void CombineAdjacentCharacterDataItems(ItemContainer container)
        {
            IItem previousItem = null;
            List<IItem> itemsToRemove = new List<IItem>();
            foreach (IItem item in container.Items)
            {
                if (item is CharacterDataItem)
                {
                    if (previousItem is CharacterDataItem)
                    {
                        CharacterDataItem previousCharacterDataItem = previousItem as CharacterDataItem;
                        CharacterDataItem characterDataItem = item as CharacterDataItem;
                        previousCharacterDataItem.Data += characterDataItem.Data;
                        itemsToRemove.Add(characterDataItem);
                    }
                    else
                    {
                        previousItem = item;
                    }
                }
                else // if (item is TagItem)
                {
                    CombineAdjacentCharacterDataItems((TagItem)item);
                    previousItem = item;
                }
            }
            foreach (IItem item in itemsToRemove)
            {
                container.Items.Remove(item);
            }
        }
    }
}
